﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Algobox.JuicyLib.Net.FTP;
using Algobox.JuicyLib.IO.CSV;

namespace Algobox.JuicyLib.Apps.NewedgeDownloader
{
    public class NewedgeDownloader
    {
        public NewedgeDownloader(string hostUri, string user, string password, string archiveDir, string ftpBackupDir, string ignoreDirs, string stageUri, string dbConn, bool ftpIsPassive)
        {
            Console.WriteLine(@"
==========================
Starting NewedgeDownloader
Source:    {0}
User:      {1}
Pass:      {2}
Archive:   {3}
Stage:     {7}
Backup:    {4}
DBCon:     {5}
Ignore:    {6}
IsPassive: {8}
==========================", hostUri, user, password, ARCHIVE_DIR, ftpBackupDir, dbConn, ignoreDirs, stageUri, ftpIsPassive);
            
            FTP_HOST = hostUri;
            FTP_USER = user;
            FTP_PASS = password;
            FTP_BACKUP_DIR = ftpBackupDir;
            FTP_STAGE_DIR = stageUri;

            if (archiveDir[archiveDir.Length - 1] != '/')
                ARCHIVE_DIR = archiveDir + '/';
            else
                ARCHIVE_DIR = archiveDir;

            if (!JuicyLib.Net.FTP.JuicyFTP.IsFTPUri(FTP_HOST))
            {
                Log("CheckURI", FTP_HOST, "FTP host is a malformed Uri : " + FTP_HOST);
                LogError("FTP host is a malformed Uri : " + FTP_HOST);
                return;
            }

            var ignoreDirList = ignoreDirs.Trim().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();

            _FTP = new JuicyFTP(user, password, hostUri, ftpBackupDir, ignoreDirList, JuicyFTP.FTPMode.Unix, ftpIsPassive ? JuicyFTP.FTPCommand.Passive : JuicyFTP.FTPCommand.Active);

            JuicyLib.SQL.LiquidView.LiquidViewHelper.LiquidViewLinq = new SQL.LiquidView.LiquidViewLinqDataContext(dbConn);

        }

        readonly string FTP_HOST;
        readonly string FTP_USER;
        readonly string FTP_PASS;
        readonly string FTP_STAGE_DIR;
        readonly string FTP_BACKUP_DIR;
        readonly string ARCHIVE_DIR;

        const int FILENAME_DATE_LEN = 8;

        readonly Algobox.JuicyLib.Net.FTP.JuicyFTP _FTP;

        const string FILE_ASSETVALUATION = "Asset_Valuation";
        const string FILE_CORAXPENDINGDIVIDENDS = "Corax_Pending_Dividends";
        const string FILE_DIVIDENDACCRUAL = "Dividend_Accrual";
        const string FILE_DAILYTRADES = "Daily_Trades";
        const string FILETYPE_CSV = ".csv";

        private DateTime? _assetValuationDate;

        public void Log(string action, string fileName, string description)
        {
            Console.WriteLine(description);
#if !DEBUG
            JuicyLib.SQL.LiquidView.LiquidViewHelper.LogGeneral("NewedgeDownloader", action, fileName, description, null, null, null, null, null, null);
#endif
        }

        public void LogError(string message)
        {
            Console.WriteLine(message);
#if !DEBUG
            JuicyLib.SQL.LiquidView.LiquidViewHelper.LogException("NewedgeDownloader", message);
#endif
        }

        //
        // MLX is a UNIX FTP server
        //
        // drwx------ 1 mailbox group           1024 Jul 31 11:08 archives
        // -rw------- 1 message group           1097 Jul 31 04:40 confirm.txt.1240.asc.09.07.31_04.40


        public void Run()
        {
            if (!CheckDirectory(String.Empty))
            {
                Log("CheckDirectory", ARCHIVE_DIR, "Archive directory does not exist : " + ARCHIVE_DIR);
                LogError("Archive directory does not exist : " + ARCHIVE_DIR);
                return;
            }

            List<string> fileList;
            _assetValuationDate = null;

            if (_FTP.TryGetFileListRecursive("", out fileList))
            {
                foreach (string file in fileList)
                {                    
                    // filter file name you don't want
                    if (!file.Contains("htaccess"))
                    {
                        // process the file
                        Console.WriteLine("Found: " + file);
                        ProcessFileNew(file);
                    }
                }

                if (_assetValuationDate.HasValue)
                {
                    ProcessAssetValuationEmail();
                }
            }
        }


        private void ProcessFileNew(string fileName)
        {
            string dateDir;
            string archiveDir = ARCHIVE_DIR;
            string data;
            string baseFilename;
            string date;
            DateTime fileDate = DateTime.Now.Date;

            // get/create actual directory for this file
            // depending on its date
            if (GetDirPath(fileName, out dateDir, out date))
            {
                // final destination directory to save this file
                archiveDir += dateDir;
                // final file name to save the file
                baseFilename = Path.GetFileName(fileName);

                if (!DateTime.TryParseExact(date,
                    "yyyyMMdd",
                    System.Globalization.CultureInfo.InvariantCulture,
                    System.Globalization.DateTimeStyles.None,
                    out fileDate))
                {
                    Log("ProcessFile", archiveDir + "/" + baseFilename, "Failed to parse date for " + date);
                    return;
                }

                bool overwrite = CheckFileExists(archiveDir, baseFilename);
                bool success = false;

                try
                {
                    Stream stream;
                    Uri stagedFile;

                    // attempt to place file in staging area
                    if (!_FTP.TryStageFile(fileName, FTP_STAGE_DIR, out stagedFile, out stream))
                    {
                        Console.WriteLine("Failed to stage... skipping");
                        return;
                    }
                                       
                    if (overwrite)
                        Log("ProcessFile", baseFilename, "Overwrite file : " + archiveDir + "/" + baseFilename);
                    else
                        Log("ProcessFile", baseFilename, "Save file : " + archiveDir + "/" + baseFilename);

                    // save file locally in archive directory
                    try
                    {
                        using (BufferedStream reader = new BufferedStream(stream))
                        {
                            using (FileStream writer = new FileStream(archiveDir + "/" + baseFilename, FileMode.Create))
                            {
                                byte[] buffer = new byte[4096];
                                int bytesRead;
                                
                                while ((bytesRead = reader.Read(buffer, 0, buffer.Length)) > 0)
                                {
                                    writer.Write(buffer, 0, bytesRead);
                                }
                            }
                        }
                    }
                    catch
                    {
                        LogError("ProcessFile failed to archive file to " + archiveDir + "/" + baseFilename);
                    }

                    // dispose of file stream
                    stream.Dispose();
                    stream = null;

                    // move file from staging URI to backup URI
                    _FTP.TryBackupStagedFile(fileName, FTP_BACKUP_DIR, stagedFile);
                                        
                }
                catch (Exception e)
                {
                    Log("ProcessFile", archiveDir + "/" + baseFilename, "Archiving file. Caught exception : " + e.Message);
                }

                try
                {
                    // read local file into database if applicable
                    if (baseFilename.Contains(FILE_ASSETVALUATION + date + FILETYPE_CSV))
                    {
                        data = File.ReadAllText(archiveDir + "/" + baseFilename);
                        Console.WriteLine("Processing: Asset Valuation File");
#if !DEBUG
                        FileAssetValuation.ProcessData(data, fileDate);
                        _assetValuationDate = fileDate;
#endif
                    }
                    else if (baseFilename.Contains(FILE_DIVIDENDACCRUAL + date + FILETYPE_CSV))
                    {
                        data = File.ReadAllText(archiveDir + "/" + baseFilename);
                        Console.WriteLine("Processing: Dividend Accrual File");
#if !DEBUG
                        FileDividendAccrual.ProcessData(data, fileDate);
#endif
                    }
                    else if (baseFilename.Contains(FILE_DAILYTRADES + date + FILETYPE_CSV))
                    {
                        data = File.ReadAllText(archiveDir + "/" + baseFilename);
                        Console.WriteLine("Processing: Daily Trades File");
#if !DEBUG
                        FileDailyTrades.ProcessData(data, fileDate);
#endif
                    }
                    else if (baseFilename.Contains(FILE_CORAXPENDINGDIVIDENDS + date + FILETYPE_CSV))
                    {
                        data = File.ReadAllText(archiveDir + "/" + baseFilename);
                        Console.WriteLine("Processing: Corax Pending Dividends File");
#if !DEBUG
                        FileCoraxPendingDividends.ProcessData(data, fileDate);
#endif
                    }

                    success = true;
                    
                }
                catch (Exception e)
                {
                    Log("ProcessFile", archiveDir + "/" + baseFilename, "Reading file... Caught exception : " + e.Message);
                }

                if (!success)
                {
                    Log("ProcessFile", archiveDir + "/" + baseFilename, "Failed to get file : " + archiveDir + "/" + baseFilename);
                    LogError("ProcessFile - Failed to get file : " + archiveDir + "/" + baseFilename);
                }

            }
            else
            {
                Log("ProcessFile", archiveDir, "Failed to get dir : " + archiveDir);
            }

            return;
        }



        private void ProcessFile(string fileName)
        {
            string dateDir;
            string archiveDir = ARCHIVE_DIR;
            string data;
            string baseFilename;
            string date;
            string message;
            DateTime fileDate = DateTime.Now.Date;

            // get/create actual directory for this file
            // depending on its date
            if (GetDirPath(fileName, out dateDir, out date))
            {
                // final destination directory to archive this file
                archiveDir += dateDir;
                // final file name to save the file
                baseFilename = Path.GetFileName(fileName);
                
                if (!DateTime.TryParseExact(date, 
                    "yyyyMMdd",
                    System.Globalization.CultureInfo.InvariantCulture,
                    System.Globalization.DateTimeStyles.None,
                    out fileDate))
                {
                    Log("ProcessFile", archiveDir + "/" + baseFilename, "Failed to parse date for " + date);
                    return;
                }

                bool overwrite = CheckFileExists(archiveDir, baseFilename);
                bool success = false;

                try
                {
                    // get the file
                    Stream stream = null;
                    if (_FTP.GetFileAsStream(fileName, out stream))
                    {
                        if (overwrite)
                            Log("ProcessFile", archiveDir + "/" + baseFilename, "Overwrite file : " + archiveDir + "/" + baseFilename);
                        else
                            Log("ProcessFile", archiveDir + "/" + baseFilename, "Save file : " + archiveDir + "/" + baseFilename);

                        try
                        {
                            using (BufferedStream reader = new BufferedStream(stream))
                            {
                                using (FileStream writer = new FileStream(archiveDir + "/" + baseFilename, FileMode.Create))
                                {
                                    byte[] buffer = new byte[4096];
                                    int bytesRead;

                                    while ((bytesRead = reader.Read(buffer, 0, buffer.Length)) > 0)
                                    {
                                        writer.Write(buffer, 0, bytesRead);
                                    }
                                }
                            }
                        }
                        catch
                        {
                            LogError("ProcessFile failed to archive file to " + archiveDir + "/" + baseFilename);
                        }
                        stream.Dispose();

                        if (baseFilename.Contains(FILE_ASSETVALUATION + date + FILETYPE_CSV))
                        {
                            data = File.ReadAllText(archiveDir + "/" + baseFilename);
                            Console.WriteLine("Processing: Asset Valuation File");
#if !DEBUG
                            FileAssetValuation.ProcessData(data, fileDate);                            
                            _assetValuationDate = fileDate;
#endif
                        }
                        else if (baseFilename.Contains(FILE_DIVIDENDACCRUAL + date + FILETYPE_CSV))
                        {
                            data = File.ReadAllText(archiveDir + "/" + baseFilename);
                            Console.WriteLine("Processing: Dividend Accrual File");
#if !DEBUG
                            FileDividendAccrual.ProcessData(data, fileDate);
#endif
                        }
                        else if (baseFilename.Contains(FILE_DAILYTRADES + date + FILETYPE_CSV))
                        {
                            data = File.ReadAllText(archiveDir + "/" + baseFilename);
                            Console.WriteLine("Processing: Daily Trades File");
#if !DEBUG
                            FileDailyTrades.ProcessData(data, fileDate);
#endif
                        }
                        else if (baseFilename.Contains(FILE_CORAXPENDINGDIVIDENDS + date + FILETYPE_CSV))
                        {
                            data = File.ReadAllText(archiveDir + "/" + baseFilename);
                            Console.WriteLine("Processing: Corax Pending Dividends File");
#if !DEBUG
                            FileCoraxPendingDividends.ProcessData(data, fileDate);
#endif
                        }
                        
                        success = true;

                        if(!_FTP.TryBackupFile(fileName, FTP_BACKUP_DIR + dateDir + "/", out message))
                        {
                            LogError("ProcessFile failed to backup file Message(" + message + ")");
                        }
                    }
                }
                catch(Exception e)
                {
                    Log("ProcessFile", archiveDir + "/" + baseFilename, "Caught exception : " + e.Message);
                }

                if(!success)
                {
                    Log("ProcessFile", archiveDir + "/" + baseFilename, "Failed to get file : " + archiveDir + "/" + baseFilename);
                    LogError("ProcessFile - Failed to get file : " + archiveDir + "/" + baseFilename);
                }
                
            }
            else
            {
                Log("ProcessFile", archiveDir, "Failed to get dir : " + archiveDir);
            }

            return;
        }


        private bool GetDirPath(string fileName, out string dirName, out string date)
        {
            try
            {
                date = fileName.Substring(fileName.Length - 4 - FILENAME_DATE_LEN, FILENAME_DATE_LEN);

                dirName = date.Insert(6, "/");
                dirName = dirName.Insert(4, "/");

                if (CheckDirectory(dirName))
                    return true;
            }
            catch (Exception e)
            {
                Log("GetDirPath", fileName, "Failed to get directory path from : " + fileName);

                dirName = String.Empty;
                date = String.Empty;
            }

            return false;
        }
        

        private bool CheckDirectory(string subDirectory)
        {
            if (subDirectory != String.Empty && subDirectory[0] == '/')
                subDirectory = subDirectory.Substring(1);

            string directory = ARCHIVE_DIR + subDirectory;

            try
            {
                if (Directory.Exists(directory))
                    return true;

                Directory.CreateDirectory(directory);
                return true;
            }
            catch (Exception e)
            {
                Log("CheckDirectory", ARCHIVE_DIR, "Create directory failed with message : " + e.Message);
                return false;
            }
        }


        private bool CheckFileExists(string directory, string fileName)
        {
            if (fileName != String.Empty && fileName[0] == '/')
                fileName = fileName.Substring(1);

            if (directory[directory.Length - 1] != '/')
                directory = directory + '/';

            if (File.Exists(directory + fileName))
                return true;

            return false;
        }


        private bool SaveString(string directory, string fileName, string data)
        {
            if (fileName != String.Empty && fileName[0] == '/')
                fileName = fileName.Substring(1);

            if (directory[directory.Length - 1] != '/')
                directory = directory + '/';

            try
            {                
                File.AppendAllText(directory + fileName, data);                
                return true;
            }
            catch
            {
                return false;
            }
        }

        
        private void ProcessAssetValuationEmail()
        {
            if (_assetValuationDate.HasValue)
            {
                if (JuicyLib.SQL.LiquidView.LiquidViewHelper.TryRunAlertReconciliationNewedgeAssetAllocationEmail(_assetValuationDate.Value))
                {
                    Console.Write("Sending Asset Valuation Email: Success");
                }
                else
                {
                    Console.Write("Sending Asset Valuation Email: Failed");
                    LogError("ProcessAssetValuation - Failed to run TryRunAlertReconciliationNewedgeAssetAllocationEmail for " + _assetValuationDate.Value);
                }
            }
        }
    }
}
